STM32

您所在的位置:网站首页 modbus rtu功能码与地址 STM32

STM32

2023-12-31 03:05| 来源: 网络整理| 查看: 265

文章目录 一、本文主要内容二、使用modbus通信协议在线修改STM32波特率(一)STM32标准库在线修改串口波特率(二)STM32HAL库-485-modbus-rtu通信在线修改串口波特率1、STM32F103ZET6芯片(1)HAL库下参考标准库形式修改波特率(2)直接修改波特率寄存器(modbus通信修改串口2的波特率) 2、stm32-简易版modbus协议通信实现波特率修改3、STM32F030K6T6芯片在线修改波特率(和STM32F1有点差异) 三、EEPROM存储数据实现单片机断电时数据保护(一)主要实现功能简介+演示1、功能介绍2、效果演示 (二)EEPROM简单读写测试1、STM32cubemx配置IIC-USART-TIM-GPIO2、代码编写测试 (三)每个地址存储一个继电器的状态(存储8个继电器状态)实现上电后数据恢复1、判断是执行下载程序还是执行断电恢复程序2、main.c文件程序编写(1)新建变量(2)读取和写入单个寄存器函数(3)下载完程序需要执行的函数(4)编写断电恢复需要执行的函数(5)条件判断函数 3、modbus.c文件程序修改(1)缓冲数组数据清零(2)EEPROM中写入数据函数(数据写入+继电器控制)(3)MODBUS-06功能码函数(加入波特率在线修改+调用数据存储函数)(4)MODBUS-16功能码函数(加入数据存储)(5)综上实现的主要功能 (四)每个地址存储8个继电器的状态(1字节=8位=8个继电器)1、main.c文件程序编写(1)定义变量(2)读取和写入单个寄存器函数(同上不变)(3)下载完程序需要执行的函数(4)断电恢复需要执行的函数(5)条件判断函数(6)main函数 2、modbus.c文件程序编写(原来基础之上)(1)变量保持不变(2)数据存储+继电器控制函数(3)MODBUS-06功能码函数(原基础修改)(4)modbus-16功能码函数(原基础修改) 四、modbus协议功能码06和10进行完善(加入对继电器的控制+数据存储)(一)两个数据存储函数(二)主要实现修改单个寄存器数据(控制继电器状态1-打开,0-关闭)(三)实现10功能码控制多个继电器的打开和关闭 五、根据说明书功能进行修改完善(一)加入功能码0x01实现继电器的状态查询或者光耦输入状态查询(二)加入功能码0x05实现继电器的控制(三)加入功能码0x0F实现继电器的全开或者全闭功能(四)修改功能码0x10实现继电器的闪开和闪闭功能

一、本文主要内容 使用STM32设备作为Modbus-RTU通信中的从机设备使用Modbus-poll模拟上位机进行数据通信STM32在运行中加入波特率的在线修改加入EEPROM进行数据存储(实现断电保护,设备重新上电时恢复到断电前的状态)实现03-06-16功能码的测试实现01-05-15功能码的补充和测试

在这里插入图片描述

测试01功能码(读取8个继电器的状态)

读多个位,有8路继电器

测试03功能码(读取多个寄存器数据)

读取多个寄存器数据

测试05功能码(实现对继电器的控制)

写入FF00打开,写入0000关闭

测试06功能码(实现对单个寄存器数据的读写来控制继电器)

写入1打开,写入0关闭(或者写入其他数值实现寄存器数据的修改)

测试15功能码(实现8路继电器的全关和全闭)

只能控制8路继电器同时开或同时关闭(未加入其他处理代码)

测试16功能码(实现继电器的闪开和闪闭) 最初版16功能码函数可以实现控制多个继电器状态(也就是一条指令修改多个寄存器的数据) 修改版16功能码函数实现继电器的闪开和闪闭

本文内容是基于下面这篇博客为基础进行延伸介绍

STM32+RS485+Modbus-RTU(主机模式+从机模式)-标准库/HAL库开发-链接

二、使用modbus通信协议在线修改STM32波特率 (一)STM32标准库在线修改串口波特率

1. STM32f103ZET6(其他芯片通用)

STM32在运行过程中一般而言串口的波特率是不需要进行修改的,但是在某些情况下需要对正在运行的STM32进行波特率修改,以此来实现后续的数据通信。

其中STM32标准库下修改串口波特率参考链接如下: STM32单片机修改串口波特率-参考博文

上文参考链接介绍的也比较清楚,就是相当于重新构造了一个串口初始化函数,在原来串口初始化函数的基础之上加入串口的失能和使能,在修改串口参数时先调用串口失能函数将对应的串口失能进行关闭串口,配置完参数之后,再调用串口使能函数将串口使能把串口给重新打开。

正点原子代码的串口初始化函数如下:

void uart_init(u32 bound){ //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure;//GPIO结构体指针 USART_InitTypeDef USART_InitStructure;//串口结构体指针 NVIC_InitTypeDef NVIC_InitStructure;//中断分组结构体指针 //1、使能串口时钟,串口引脚时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟 //2、复位串口 USART_DeInit(USART1); //复位串口1 //3、发送接收引脚的设置 //USART1_TX PA.9(由图 可知设置为推挽复用输出) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9 //USART1_RX PA.10(有图可知浮空输入) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10 //4、USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//一般设置为9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 #if EN_USART1_RX //如果使能了接收 //5、Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //6、开启中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断 #endif //7、使能串口 USART_Cmd(USART1, ENABLE); //使能串口 }

提取里面的串口配置部分重新改一个修改波特率函数

修改串口参数的关键点所在是修改参数前先将串口关闭(也就是串口失能),修改完串口参数之后再去将串口重新打开(串口使能),之所以如此是为了防止数据传输错误。 在这里插入图片描述 串口波特率修改函数如下:

void uart_init_reset(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure;//GPIO结构体指针 USART_InitTypeDef USART_InitStructure;//串口结构体指针 USART_Cmd(USART1, DISABLE); //失能串口 //4、USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//一般设置为9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位 USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 USART_Cmd(USART1, ENABLE); //使能串口 }

如果只需要修改为某个固定的波特率,直接调用函数写入就好,比如说将波特率修改为11500,则调用修改波特率函数写入相关参数的数值为115200即可:

huart_init_reset(115200)

修改串口波特率往往是通过指令来进行修改的,不同的指令代表不同的波特率。

根据相关的指令去配置波特率。指令会有很多个,所以还需要提前准备一个波特率数组存放波特率,当通过条件判断接收到不同的控制指令时,直接将波特率数组中相对应的波特率参数写入函数中即可。

//通过串口接收到的数据自定义修改波特率 uint32_t bound_table[]={4800,9600,19200,115200};

既然是串口测试,那就可以根据串口接收到的指令去修改stm32的波特率(直接在正点原子的串口实验进行修改) 通过串口助手分别给单片机发送a,b,c,d指令,其对应的波特率如下:

a---4800--------bound_table[0] b---9600--------bound_table[1] c---19200-------bound_table[2] d---115200------bound_table[3]

可以通过if-else条件语句或者switch语句进行判断,此处使用if条件语句

if(USART_RX_BUF[0]=='a') { uart_init_reset(bound_table[0]); } if(USART_RX_BUF[0]=='b') { uart_init_reset(bound_table[1]); } if(USART_RX_BUF[0]=='c') { uart_init_reset(bound_table[2]); } if(USART_RX_BUF[0]=='d') { uart_init_reset(bound_table[3]); }

综上:正点原子串口实验修改主函数代码如下:

int main(void) { u8 t; u8 len; u16 times=0; delay_init(); //延时函数初始化 NVIC_Configuration(); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 uart_init(9600); //串口初始化为9600 LED_Init(); //LED端口初始化 KEY_Init(); //初始化与按键连接的硬件接口 while(1) { if(USART_RX_STA&0x8000)//最高位是1表示一次接收完毕 { len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度(0011 1111 1111 1111) if(USART_RX_BUF[0]=='a') { uart_init_reset(bound_table[0]); } if(USART_RX_BUF[0]=='b') { uart_init_reset(bound_table[1]); } if(USART_RX_BUF[0]=='c') { uart_init_reset(bound_table[2]); } if(USART_RX_BUF[0]=='d') { uart_init_reset(bound_table[3]); } printf("\r\n您发送的消息为:\r\n\r\n");//打印数据到串口 for(t=0;tInit.OverSampling == UART_OVERSAMPLING_16) { if (huart->Instance == USART2) { pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); printf("2222"); } else { pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); } } else { if (huart->Instance == USART2 ) { pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); } else { pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); } } }

依旧是在modbus-rtu通信的06功能码处理函数中调用,修改代码如下: 在这里插入图片描述

2、stm32-简易版modbus协议通信实现波特率修改

主控芯片:stm32f013zet6,串口1通过modbus协议通信修改STM32波特率(单纯的串口通信,没有加入485通信)

第一步:

使用STM32cubemx生成包含LED引脚,串口1的代码初始化框架

第二步:

实现串口自定义形式的数据接收的指令判别,通过简单的modbus协议控制指令实现控制LED灯的亮灭。

第三步:

在控制LED灯亮灭的基础之上实现程序运行过程中对波特率的修改 波特率修改效果如下: 在这里插入图片描述

第一步:STM32cubemx对STM32F103ZET6的配置

配置时钟RCC

在这里插入图片描述

配置sys选项

如果不选择的话程序使用仿真器下载一次,后续将无法使用仿真器下载,(如果不小心忘了配置导致后续无法使用仿真器修改程序,则可以通过mcuisp下载程序,下载一次之后仿真器即可正常下载了) 在这里插入图片描述

LED引脚配置:将PB8引脚设置为推挽输出模式

在这里插入图片描述

配置串口1,其他参数默认,并且使能串口中断

在这里插入图片描述

在这里插入图片描述

clock配置

输入72键盘回车自动配置即可 在这里插入图片描述 完成以上步骤即可完成使用STM32cubemx的初始化配置,直接生成代码即可

第二步:通过简单的modbus协议实现对led灯的控制 串口接收的数据共四位:包含帧头(第一位aa)、帧尾(最后一位55)、设备码(第二位01)、数据指令(00或01) 指令如下:

串口助手发送 aa 01 00 55 是打开led 串口助手发送 aa 01 01 55 是关闭led 串口助手发送其他的报错提示重新输入

对main,c文件进行操作:

定义变量

在这里插入图片描述

/* USER CODE BEGIN PV */ uint8_t rxbuffer[4];//接收缓冲区 uint8_t rx_flag=0;//接收完成标志:0表示接收未完成,1表示接收完成 uint8_t errflag=0;//指令错误标志:0表示指令正确,1表示错误 /* USER CODE END PV */

重定义printf和串口接收回调函数的处理

printf重定义记得要调用头文件#include “stdio.h” 串口中断回调函数主要作用是清除数据接收完成标志和重新启动串口中断接收 在这里插入图片描述

/* USER CODE BEGIN 4 */ //重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数 int fgetc(FILE *f) { int ch; HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xfff); return (ch); } //重定义fputc函数 int fputc(int ch, FILE *f) { //采用轮询方式发送1字节数据 HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xfff); return ch; } //接收回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance==USART1)//判断发生接收中断的串口 { rx_flag=1;//置位接收完成标志 HAL_UART_Receive_IT(&huart1,(uint8_t*)rxbuffer,4);//使能接收中断 } } /* USER CODE END 4 */

对main主函数进行修改:

while循环之前进行输入提示和使能接收中断(必须打开) 在这里插入图片描述 while循环里面就是在接收数据完成的情况下,判断数据帧头帧尾是否正确,如果正确则再去判断设备码是否正确,如果设备码也正确,再去判断收到的数据指令是打开LED还是关闭LED,代码如下

/* USER CODE BEGIN 3 */ if(rx_flag==1)//表示接收完成 { rx_flag=0;//清除接收完成标志位 if(rxbuffer[0]==0xaa&&rxbuffer[3]==0x55)//判断帧头帧尾 { if(rxbuffer[1]==0x01)//判断设备码 { if(rxbuffer[2]==0x00)//判断功能码-开启 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//开灯 printf("LED is open\r\n"); } else if(rxbuffer[2]==0x01)//关闭 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//关灯 printf("LED is close\r\n"); } else//功能码错误 { errflag=1;//置位错误标志 } } else//设备码错误 { errflag=1;//置位错误标志 } } else//帧头帧尾错误 { errflag=1;//置位错误标志 } if(errflag==1)//发送错误提示信息 { printf("communication error ~pelase send again!\r\n"); } //清除接收缓冲区和错误标志,准备下一次接收 errflag=0; rxbuffer[0]=0; rxbuffer[1]=0; rxbuffer[2]=0; rxbuffer[3]=0; } } /* USER CODE END 3 */

编译烧写程序即可:

指令如下(十六进制形式发送) 串口助手发送 aa 01 00 55 是打开led 串口助手发送aa 01 01 55 是关闭led 输入其他的报错提示重新输入

第三步:在线修改stm32波特率 在上面基础之上通过串口1接收到的数据实现波特率的修改,波特率修改函数如下:

//在程序运行时修改串口波特率 //串口1是APB2总线,其他串口是APB1总线 void USART_BRR_Configuration(UART_HandleTypeDef *huart, uint32_t BaudRate) { uint32_t pclk; UART_OVERSAMPLING_16; huart->Init.BaudRate = BaudRate; if (huart->Init.OverSampling == UART_OVERSAMPLING_16) { if (huart->Instance == USART1) { pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); } else { pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING16(pclk, huart->Init.BaudRate); } } else { if (huart->Instance == USART1) { pclk = HAL_RCC_GetPCLK2Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); } else { pclk = HAL_RCC_GetPCLK1Freq(); huart->Instance->BRR = UART_BRR_SAMPLING8(pclk, huart->Init.BaudRate); } } }

主函数中修改波特率部分如图: modbus通信指令(十六进制)

第一位aa表示串口接收数据帧的帧头 第二位01表示设备码 第三位是数据为00(打开led灯和修改波特率为4800)和01(关闭led灯和修改波特率为9600) 第四位数据是帧尾 使用串口助手分别发送一下数据指令(十六进制发送) aa 01 00 55 打开led灯和修改波特率为4800 aa 01 00 55 关闭led灯和修改波特率为9600

在这里插入图片描述

整个 主函数代码如下:

int main(void) { /* USER CODE BEGIN 1 */ int i=0; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ printf("*****communication frame*******\r\n"); printf("please enter instruction:\r\n"); HAL_UART_Receive_IT(&huart1,(uint8_t*)rxbuffer,4);//使能接收中断 /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if(rx_flag==1)//表示接收完成 { rx_flag=0;//清除接收完成标志位 if(rxbuffer[0]==0xaa&&rxbuffer[3]==0x55)//判断帧头帧尾 { if(rxbuffer[1]==0x01)//判断设备码 { if(rxbuffer[2]==0x00)//判断功能码-开启 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//开灯 printf("LED is open\r\n"); //3-修改波特率成功 printf("boud=4800\r\n"); USART_BRR_Configuration(&huart1,4800); } else if(rxbuffer[2]==0x01)//关闭 { printf("boud=9600\r\n"); USART_BRR_Configuration(&huart1,9600); HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//关灯 printf("LED is close\r\n"); } else//功能码错误 { errflag=1;//置位错误标志 } } else//设备码错误 { errflag=1;//置位错误标志 } } else//帧头帧尾错误 { errflag=1;//置位错误标志 } if(errflag==1)//发送错误提示信息 { printf("communication error ~pelase send again!\r\n"); } //清除接收缓冲区和错误标志,准备下一次接收 errflag=0; rxbuffer[0]=0; rxbuffer[1]=0; rxbuffer[2]=0; rxbuffer[3]=0; } } /* USER CODE END 3 */ }

编译下载程序通过串口助手即可完成波特率的修改,效果如下: 在这里插入图片描述

3、STM32F030K6T6芯片在线修改波特率(和STM32F1有点差异)

stm32-hal库-modbus-RTU通信在线修改波特率-代码下载链接 因为手中modbus通信的主控芯片是STM32F030K6T6所以参考以上进行修改 修改波特率的过程依旧参考cubemx生成的串口初始化代码去模仿着自己写一个波特率修改函数

在这里插入图片描述 进入串口初始化原函数之后找到下面参数配置函数跳转 在这里插入图片描述 跳转到原函数之后找到波特率参数配置位置,和STM32F103的有点不一样,还是摘取有用的部分即可 在这里插入图片描述 在这里插入图片描述 参考以上代码仿写波特率修改函数如下:

//修改波特率函数 void USART_BRR_Configuration(UART_HandleTypeDef *huart,uint32_t BaudRate) { huart->Init.BaudRate = BaudRate; uint32_t pclk; if (huart->Init.OverSampling == UART_OVERSAMPLING_8) { pclk = HAL_RCC_GetPCLK1Freq(); /* USARTDIV must be greater than or equal to 0d16 */ if (pclk != 0U) { huart->Instance->BRR =(uint16_t)(UART_DIV_SAMPLING8(pclk, huart->Init.BaudRate)); } } else { pclk = HAL_RCC_GetPCLK1Freq(); if (pclk != 0U) { /* USARTDIV must be greater than or equal to 0d16 */ huart->Instance->BRR =(uint16_t)(UART_DIV_SAMPLING16(pclk, huart->Init.BaudRate)); } } }

06功能码可以实现缓冲数组数据的修改,把数组中的某一位定义为了波特率位,通过06功能码对这个数据修改后即可实现对波特率的修改。 在这里插入图片描述

完整代码如下(红色框选为新加内容)

在这里插入图片描述

// Modbus 6号功能码函数 // Modbus 主机写入寄存器值 void Modbus_Func6() { uint16_t Regadd; uint16_t val; uint16_t i,crc,j; i=0; Regadd=modbus.rcbuf[2]*256+modbus.rcbuf[3]; val=modbus.rcbuf[4]*256+modbus.rcbuf[5]; Reg[Regadd]=val; //以下为回应主机 modbus.sendbuf[i++]=modbus.myadd; modbus.sendbuf[i++]=0x06; modbus.sendbuf[i++]=Regadd/256; modbus.sendbuf[i++]=Regadd%256; modbus.sendbuf[i++]=val/256; modbus.sendbuf[i++]=val%256; crc=Modbus_CRC16(modbus.sendbuf,i); modbus.sendbuf[i++]=crc/256; modbus.sendbuf[i++]=crc%256; //数据发送包打包完毕 RS485_TX;//使能485控制端(启动发送) for(j=0;j


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3